from usr import fonts
from usr import icons


class Canvas:
    """
    画布，实现图形功能
    TODO: 实现显示缓存
    """

    def __init__(self, lcd, width, height):
        self.lcd = lcd
        self.lcd_w = width
        self.lcd_h = height

    def draw_char(self, x, y, fw, fh, ch_buf, fc, bc):
        """
        单个字符显示，包括汉字和ASCII

        :param x: x轴坐标
        :type x: int
        :param y: y轴坐标
        :type y: int
        :param fw: 字体宽度
        :type y: int
        :param fh: 字体高度
        :type y: int
        :param ch_buf: 存放汉字字模的元组或者列表
        :param fc: 字体颜色，RGB565
        :type y: int
        :param bc: 背景颜色，RGB565
        :type y: int
        """
        rgb_buf = []
        t1 = fw // 8
        t2 = fw % 8
        if t2 != 0:
            xsize = (t1 + 1) * 8
        for i in range(0, len(ch_buf)):
            for j in range(0, 8):
                if (ch_buf[i] << j) & 0x80 == 0x00:
                    rgb_buf.append(bc & 0xff)
                    rgb_buf.append(bc >> 8)
                else:
                    rgb_buf.append(fc & 0xff)
                    rgb_buf.append(fc >> 8)
        data = bytearray(rgb_buf)
        self.lcd.lcd_write(data, x, y,
                           x + fw - 1, y + fh - 1)
        rgb_buf.clear()
        del rgb_buf

    def draw_ascii(self, x, y, fw, fh, ch, fc, bc):
        """
        ASCII字符显示,目前支持8x16、16x24的字体大小，
        如果需要其他字体大小需要自己增加对应大小的字库数据，并
        在下面函数中增加这个对应字库的字典。
        :param x: x轴显示起点
        :param y: y轴显示起点
        :param fw: 字体宽度
        :param fh: 字体高度
        :param ch: 待显示的ASCII字符
        :param fc: 字体颜色，RGB565
        :param bc: 背景颜色，RGB565
        """
        ascii_dict = {}
        if fw == 8 and fh == 16:
            ascii_dict = fonts.ascii_8x16_dict
        elif fw == 16 and fh == 24:
            ascii_dict = fonts.ascii_16x24_dict

        for key in ascii_dict:
            if ch == key:
                self.draw_char(x, y, fw, fh, ascii_dict[key], fc, bc)

    def draw_ascii_text(self, x, y, fw, fh, txt, fc, bc):
        """显示字符串,目前支持8x16的字体大小，
        如果需要其他字体大小需要自己增加对应大小的字库数据，并
        在lcd_show_ascii函数中增加这个对应字库的字典。

        :param x: x轴坐标
        :type x: int
        :param y: y轴坐标
        :type y: int
        :param fw: 字体宽度
        :type fw: int
        :param fh: 字体高度
        :type fh: int
        :param txt: 待显示的 ASCII 字符串
        :type txt: str
        :param fc: 字体颜色，RGB565
        :type fc: int
        :param bc: 背景颜色，RGB565
        :type bc: int
        """
        xs = x
        ys = y
        if (len(txt) * fw + x) > self.lcd_w:
            raise Exception('Display out of range')
        for ch in txt:
            self.draw_ascii(xs, ys, fw, fh, ch, fc, bc)
            xs += fw

    def draw_chinese(self, x, y, fw, fh, ch, fc, bc):
        """汉字显示,目前支持16x16、16x24、24x24的字体大小，
        如果需要其他字体大小需要自己增加对应大小的字库数据，并
        在下面函数中增加这个对应字库的字典。

        :param x: x轴显示起点.
        :type x: int
        :param y: y轴显示起点.
        :type y: int
        :param fw: 字体宽度
        :type fw: int
        :param fh: 字体高度
        :type fh: int
        :param ch: 字符
        :type ch: str
        :param fc: 字体颜色
        :type fc: int
        :param bc: 字体背景色
        :type bc: int
        """
        hanzi_dict = {}
        if fw == 16 and fh == 16:
            hanzi_dict = fonts.hanzi_16x16_dict
        elif fw == 16 and fh == 24:
            hanzi_dict = fonts.hanzi_16x24_dict
        elif fw == 24 and fh == 24:
            hanzi_dict = fonts.hanzi_24x24_dict

        for key in hanzi_dict:
            if ch == key:
                self.draw_char(x, y, fw, fh, hanzi_dict[key], fc, bc)

    def draw_chinese_text(self, x, y, fw, fh, txt, fc, bc):
        """显示单行中文文本

        :param x: x
        :type x: int
        :param y: y
        :type y: int
        :param fw: 字宽
        :type fw: int
        :param fh: 字高
        :type fh: int
        :param txt: 文本
        :type txt: str
        :param fc: 前景
        :type fc: int
        :param bc: 背景
        :type bc: int
        :raises Exception: 超出屏幕范围
        """
        xs = x
        ys = y
        # print('chstrlen={}, w={}'.format(len(str), self.lcd_w))
        if (len(txt) / 3 * fw + x) > self.lcd_w:
            raise Exception('Display out of range')
        for i in range(0, len(txt), 3):
            index = i + 3
            ch = txt[i:index]
            self.draw_chinese(xs, ys, fw, fh, ch, fc, bc)
            xs += fh


    def draw_text(self, x, y, txt, fc, bc):
        """显示单行文本

        :param x: x
        :type x: int
        :param y: y
        :type y: int
        :param txt: 文本
        :type str: int
        :param fc: 前景色
        :type fc: int
        :param bc: 背景色
        :type bc: int
        """
        cfw = 24
        efw = 16
        fh = 24
        xs = x
        ys = y
        i = 0
        while i < len(txt):
            ch = txt[i]
            if ord(ch) > 127:
                if xs + cfw > self.lcd_w:
                    break
                ch = txt[i: i + 3]
                self.draw_chinese(xs, ys, cfw, fh, ch, fc, bc)
                i += 3
                xs += cfw
            else:
                if xs + efw > self.lcd_w:
                    break
                self.draw_ascii(xs, ys, 16, 24, ch, fc, bc)
                xs += efw
                i += 1

    def draw_rect_text(self, x1, y1, x2, y2, txt, fc, bc):
        """自指定区域内显示字符串

        :param x1: 区域左上角x坐标
        :type x1: int
        :param y1: 区域左上角y坐标
        :type y1: int
        :param x2: 区域右下角x坐标
        :type x2: int
        :param y2: 区域右下角y坐标
        :type y2: int
        :param txt: 字符串
        :type txt: str
        :param fc: 前景颜色
        :type fc: int
        :param bc: 背景颜色
        :type bc: int
        """
        lines = txt.split('\n')
        cfw = 24
        efw = 16
        fh = 24
        lh = 32
        ys = y1
        for line in lines:
            xs = x1
            i = 0
            while i < len(line):
                if (ys + fh > y2):
                    break
                ch = line[i]
                if ord(ch) > 127:
                    if (xs + cfw > x2):
                        xs = x1
                        ys += lh
                    ch = line[i: i + 3]
                    self.draw_chinese(xs, ys, 24, 24, ch, fc, bc)
                    i += 3
                    xs += cfw
                else:
                    if xs + efw > x2:
                        xs = x1
                        ys += lh
                    self.draw_ascii(xs, ys, 16, 24, ch, fc, bc)
                    xs += efw
                    i += 1
            ys += lh
        lines.clear()
        del lines
        return ys

    def draw_icon(self, x, y, name, iw, ih, fc, bc):
        buf = icons.icon_dict[name]
        self.draw_char(x, y, iw, ih, buf, fc, bc)

    def draw_image(self, image_data, x, y, width, heigth):
        """
        图片显示
        如果图片宽高小于80x80，可直接该函数一次性写入并显示

        :param image_data: 存放待显示图片的RGB数据
        :param x: x轴显示起点
        :param y: y轴显示起点
        :param width: 图片宽度
        :param heigth: 图片高度
        """
        self.lcd.lcd_write(bytearray(image_data), x, y,
                           x + width - 1, y + heigth - 1)

    def draw_image_file(self, path, x, y, width, height, h):
        """
        图片显示
        如果图片宽高大于80x80，用该函数来分段写入显示，分段写入原理如下：
        以要显示图片的宽度为固定值，将待显示的图片分成若干宽高为 width * h 大小的图片，最后一块高度不足h的按实际高度计算，
        h为分割后每个图片的高度，可由用户通过参数 h 指定，h的值应该满足关系： width * h * 2 < 4096

        :param path: 存放图片数据的txt文件路径，包含文件名，如 '/usr/image.txt'
        :param x: x轴显示起点
        :param y: - y轴显示起点
        :param width: 图片宽度
        :param height: 图片高度
        :param h: 分割后每个图片的高度
        """
        image_data = []
        read_n = 0  # 已经读取的字节数
        byte_n = 0  # 字节数
        xs = x
        ys = y
        h_step = h  # 按高度h_step个像素点作为步长
        h1 = height // h_step  # 当前图片按h_step大小分割，可以得到几个 width * h_step 大小的图片
        h2 = height % h_step  # 最后剩下的一块 大小不足 width * h_step 的图片的实际高度
        # print('h1 = {}, h2 = {}'.format(h1, h2))
        with open(path, "r", encoding='utf-8') as fd:
            # for line in fd.readlines():
            end = ''
            while not end:
                line = fd.readline()
                if line == '':
                    end = 1
                else:
                    curline = line.strip('\r\n').strip(',').split(',')
                    for i in curline:
                        byte_n += 1
                        read_n += 1
                        image_data.append(int(i))
                        if h1 > 0 and byte_n == width * h_step * 2:
                            self.lcd_show_image(
                                image_data, xs, ys, width, h_step)
                            image_data = []
                            ys = ys + h_step
                            h1 -= 1
                            byte_n = 0
                            # print('image_data len = {}'.format(len(image_data)))
                        elif h1 == 0 and read_n == width * height * 2:
                            if h2 != 0:
                                self.lcd_show_image(
                                    image_data, xs, ys, width, h2)

    def get_rgb565_color(self, r, g, b):
        """将24位色转换位16位色
        如红色的24位色为0xFF0000，则r=0xFF,g=0x00,b=0x00,
        将r、g、b的值传入下面函数即可得到16位相同颜色数据

        :param r: r
        :type r: int
        :param g: g
        :type g: int
        :param b: b
        :type b: int
        :return: rgb565
        :rtype: int
        """
        return ((r << 8) & 0xF800) | ((g << 3) & 0x07E0) | ((b >> 3) & 0x001F)

    def set_brightness(self, level):
        pass

    def clear(self, color):
        self.lcd.lcd_clear(color)

    def fill_rect(self, x1, y1, x2, y2, fc):
        buf = []
        for i in range(y1, y2 + 1):
            for j in range(x1, x2 + 1):
                buf.append(fc & 0xff)
                buf.append(fc >> 8)
        self.lcd.lcd_write(bytearray(buf), x1, y1, x2, y2)
        buf.clear()
        del buf

    def draw_line(self, x1, y1, x2, y2, fc):
        buf = []
        if x1 == x2:
            if y1 == y2:
                buf.append(fc & 0xff)
                buf.append(fc >> 8)
            else:
                for i in range(y1, y2 + 1):
                    buf.append(fc & 0xff)
                    buf.append(fc >> 8)
        else:
            if y1 == y2:
                for i in range(x1, x2 + 1):
                    buf.append(fc & 0xff)
                    buf.append(fc >> 8)
        self.lcd.lcd_write(bytearray(buf), x1, y1, x2, y2)
        buf.clear()
        del buf

    def draw_rect(self, x1, y1, x2, y2, fc):
        self.draw_line(x1, y1, x2, y1, fc)
        self.draw_line(x2, y1, x2, y2, fc)
        self.draw_line(x1, y2, x2, y2, fc)
        self.draw_line(x1, y1, x1, y2, fc)
